module objects.brownterrain;

import objects.triangle;
import std.math;
import std.stream;
import utils.mt19937ar;
import utils.perlin;
import utils.vector2;
import utils.vector3;

public class BrownTerrain {
    private {
        Triangle[] faces;
        TriangleTex[] faceTex;
        int[] mTriMats;
        float[] mTerrain;

        int mRes;
        int mSize;
        float mYscale;
    }

    /**
     * Params:
     *  res = heightmap resolution, will produce 2*(res-1)^2 triangles
     *  size = x-z-rect size of result (world coordinates)
     *  yscale = y (height) factor
     *  noctaves = number of perlin octaves, controls level of detail
     *             (higher -> more details)
     *  roughness = how rough/uneven the terrain will be (default 0.5)
     *              closer to 1 -> rougher, closer to 0 -> flatter
     */
    this(int res, int size, float yscale, int noctaves, float roughness = 0.5f) {
        mRes = res;
        mSize = size;
        mYscale = yscale;

        float rand = genrand_real2();
        noctaves = noctaves<1?1:noctaves;

        generateHeightmap(rand,res,noctaves,roughness);

        Vector2f tex(int x, int y) {
            Vector2f ret;
            ret.x = cast(float)x/(res-1);
            ret.y = cast(float)y/(res-1) - 1.0f;   //we will never know why...
            return ret;
        }


        Vector3f[] lastRow = new Vector3f[res];

        float peak = float.min;
        float abyss = float.max;
        for (int x = 0; x < res; x++) {
            lastRow[x] = hmapTo3d(Vector2i(x,0));
            //may be needed later
            peak = fmax(peak,lastRow[x].y);
            abyss = fmin(abyss,lastRow[x].y);
        }

        faces.length = (res-1)*(res-1)*2;
        faceTex.length = faces.length;
        mTriMats.length = faces.length;
        int idx = 0;
        for (int y = 1; y < res; y++) {
            Vector3f p;
            Vector3f pprev = hmapTo3d(Vector2i(0,y));
            peak = fmax(peak,pprev.y);
            abyss = fmin(abyss,pprev.y);
            for (int x = 1; x < res; x++) {
                p = hmapTo3d(Vector2i(x,y));

                peak = fmax(peak,p.y);
                abyss = fmin(abyss,p.y);

                faceTex[idx] = TriangleTex([tex(x,y),tex(x-1,y-1),tex(x-1,y)]);
                faces[idx++] = Triangle([p,lastRow[x-1],pprev]);
                faceTex[idx] = TriangleTex([tex(x,y),tex(x,y-1),tex(x-1,y-1)]);
                faces[idx++] = Triangle([p,lastRow[x],lastRow[x-1]]);
                lastRow[x-1] = pprev;
                pprev = p;
            }
            lastRow[res-1] = p;
        }
    }

    private void generateHeightmap(float randval, int res, int noctaves, float roughness = 0.5f) {
        mTerrain = new float[res*res];
        for (int x = 0; x < res; x++) {
            for (int y = 0; y < res; y++) {
                mTerrain[y*res+x] = terrain2(cast(float)x/res,cast(float)y/res,randval,noctaves,roughness);
            }
        }
    }

    public Vector3f hmapTo3d(Vector2i p) {
        Vector3f ret;
        ret.x = (cast(float)p.x/mRes-0.5f)*mSize;
        ret.y = mTerrain[p.y*mRes+p.x]*mYscale;
        ret.z = (cast(float)p.y/mRes-0.5f)*mSize;
        return ret;
    }

    public float heightAt(Vector2i p) {
        return mTerrain[p.y*mRes+p.x];
    }

    public int res() {
        return mRes;
    }

    public void saveToStreamRaw(Stream st) {
        assert(faces.length == faceTex.length);
        st.writeBlock(faces.ptr,faces.length*Triangle.sizeof);
        st.writeBlock(faceTex.ptr,faceTex.length*TriangleTex.sizeof);
    }
}
